feat(backend): add Cobra CLI foundation#53
Conversation
Greptile SummaryThis PR adds a Cobra-based
Confidence Score: 5/5Safe to merge. The daemon-control commands, shutdown guard, and ownership-verification logic are all well-tested, and the two prior review findings (Windows process-liveness and concurrent-start run-file clobber) are addressed. All changed paths are covered by unit tests (root_test, stop_test, exitcode_test, control_test, server_test) and a cross-platform E2E suite. The shutdown guard, PID reuse protection, and stale run-file handling are each tested by dedicated cases. No logic errors were found in the production code paths. No files require special attention. The one nit is in e2e_test.go's httpGet helper (single Read call), which is test code only and unlikely to cause flakes given the small response sizes. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant ao_start as ao start
participant ao_stop as ao stop
participant ao_status as ao status
participant RunFile as running.json
participant Daemon as ao daemon
User->>ao_start: ao start
ao_start->>RunFile: Read (inspect state)
alt already ready
RunFile-->>ao_start: PID+Port (live, owned)
ao_start-->>User: daemon ready (pid, port)
else stopped or stale
ao_start->>RunFile: Remove (if stale)
ao_start->>Daemon: spawn subprocess (detached)
loop poll /readyz every 100ms
ao_start->>Daemon: "GET /healthz -> verify service+pid"
ao_start->>Daemon: "GET /readyz -> verify service+pid"
end
Daemon->>RunFile: Write PID+Port
ao_start-->>User: daemon ready (pid, port)
end
User->>ao_status: ao status [--json]
ao_status->>RunFile: Read
ao_status->>Daemon: GET /healthz (verify owner)
ao_status->>Daemon: GET /readyz (verify owner)
ao_status-->>User: "state: ready | stale | unhealthy | not_ready | stopped"
User->>ao_stop: ao stop
ao_stop->>RunFile: Read + inspectDaemon
ao_stop->>Daemon: POST /shutdown (localControlRequest gate)
Daemon->>RunFile: Remove
loop poll until run-file gone or PID dead
ao_stop->>RunFile: Read
ao_stop->>Daemon: ProcessAlive(pid)?
end
ao_stop-->>User: daemon stopped
Reviews (8): Last reviewed commit: "test(cli): port the E2E suite to cross-p..." | Re-trigger Greptile |
The shutdown endpoint test was authored against the pre-rebase httpd.New(cfg, log) signature. After rebasing onto main, the terminal manager (from #50) made termMgr a required third arg. Pass nil — the test exercises /shutdown, not /mux, so the terminal surface stays off. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
a51140b to
0d8ffcd
Compare
… store Addresses review findings on PR #53 (on top of the rebase onto main). - doctor: stop opening/migrating SQLite. The daemon is the sole store writer/migrator (architecture.md §7); the CLI must not run migrations or open a second writer against a DB a live daemon owns. doctor now reports database-file presence and gains --json. - stop: only remove running.json when it still belongs to the PID we stopped, so a concurrent `ao start` that wrote a new run-file is not clobbered into looking stopped. - httpd: gate POST /shutdown to loopback callers with no Origin header, closing the CSRF / DNS-rebinding vector against an unauthenticated, state-changing endpoint. - start: detach the spawned daemon into its own session/process group so a Ctrl-C while `ao start` waits for readiness doesn't also kill it. - cli: exit 2 for usage errors (bad flag / arg count) vs 1 for runtime failures. - daemon: unexport newLogger (only used in-package). - tests: /shutdown guard (cross-origin + rebinding) and stop run-file ownership guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Took ownership of this PR to get it green and address the review. Rebase / conflict resolution. The branch is now rebased onto current Review fixes (2d00e46):
|
Adds a fresh-machine, install→use→verify E2E test for the `ao` CLI and wires it into CI. The suite drives the real binary (start/status/doctor/stop + the daemon-control HTTP surface) against fully isolated state — its own temp run-file, data dir, and an auto-picked free loopback port — so it never collides with a developer's real AO install or daemon. - test/cli/smoke.sh: 40 assertions covering install resolution, version/help (daemon hidden), doctor text+json (and that it does NOT migrate SQLite), status stopped/stale/ready, start fresh+idempotent, daemon-created store, /healthz identity, the /shutdown CSRF + DNS-rebinding guard (403 + survives), graceful/stale/idempotent stop, run-file ownership cleanup, exit codes (2 usage / 1 runtime), and completion for all four shells. It deliberately ignores an inherited AO_PORT and self-allocates a free port for isolation. - test/cli/Dockerfile: models installing ao on a fresh machine — builds the binary, drops it on PATH in a clean Debian image with only runtime deps (git/tmux/curl), runs the suite as a non-root user. - test/cli/run-local.sh: build-from-source + native run convenience wrapper. - .github/workflows/cli-e2e.yml: two tiers — `native` runs the suite on a ubuntu+macos runner matrix (the real VMs, to cover the unix setsid detach and macOS os.UserConfigDir paths a Linux container can't), and `container` runs the fresh-machine Docker image with --init (real PID-1 reaper so the stale-daemon assertion is reliable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stale-daemon assertion does not depend on container PID-1 reaping — it writes a fabricated dead PID rather than killing a real process. --init is still run so the real daemon spawned by the `start` test (detached via setsid) is reaped after `stop` instead of lingering as a zombie. Reword the README, Dockerfile, and workflow comments to say that accurately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the one nit from the regression audit: the exit-code wiring was correct and covered end-to-end by the smoke test, but not pinned by a unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tput run-local.sh -v (or AO_SMOKE_VERBOSE=1) makes smoke.sh echo every command and its complete output, indented, alongside the PASS/FAIL — for auditing exactly what the suite runs and what the CLI returns. Default output is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arness Replaces the growing bash smoke test with a Go os/exec suite behind the `e2e` build tag (backend/internal/cli/e2e_test.go). It builds the real binary and drives start/status/doctor/stop + the daemon-control HTTP surface against isolated state (temp dir + OS-assigned free port), and now runs natively on ubuntu + macOS + WINDOWS in CI — finally covering the Windows CREATE_NEW_PROCESS_GROUP detach path and per-OS os.UserConfigDir resolution that a Linux container can't observe. `go test -tags e2e -v` logs every command and its output, replacing the bash -v flag. - backend/internal/cli/e2e_test.go: 8 table-style TestE2E_* cases; strips any inherited AO_* env so a real daemon's AO_PORT can't leak in. - test/cli/install-check.sh: small, linear fresh-install proof the Dockerfile runs (binary on PATH, no toolchain) — kept as the hardening tier. - test/cli/Dockerfile: run install-check.sh instead of the full bash suite. - .github/workflows/cli-e2e.yml: `native` is now a go test matrix over ubuntu+macos+windows; `container` builds the image and runs it with --init. - Removes test/cli/smoke.sh and test/cli/run-local.sh (superseded by `go test`). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
aoCLI entrypoint underbackend/cmd/aointernal/daemonand keepsgo run .as a compatibility wrapperao daemon,ao start,ao stop,ao status,ao doctor,ao completion, andao versionWhat works now
ao startstarts the daemon and waits for/readyzao status/ao status --jsonreport daemon state fromrunning.jsonand health probesao stopgracefully terminates the daemon and cleans stale run-filesao doctorchecks config, data dir, SQLite migrations, daemon state, and local tool availabilityNext steps
/api/v1/projectsover a small project service.ao project list/add/show/remove./api/v1/sessionsand implementao spawn,ao session ..., andao send./eventsSSE/list reads and implementao events tail/list.Verification
git diff --checkgofmt -l . && go build ./... && go test ./...go vet ./...go test -race ./...AO_RUN_FILE,AO_DATA_DIR, andAO_PORT:--help,version,--version, all completion shells, stoppedstatus --json,doctor,start, idempotentstart --json, readystatus, readystatus --json,stop, final stoppedstatus --json